1 /**
2 DB Idea Source:
3     http://zetcode.com/db/sqlite/constraints/
4  */
5 module test.books;
6 
7 version(unittest)
8 {
9     import db_constraints;
10     import test.authors;
11 }
12 
13 version(unittest)
14 @ForeignKeyConstraint!(
15     "fk_Books_Authors_AuthorId",
16     ["AuthorId"],
17     "Authors",
18     ["AuthorId"],
19     Rule.cascade,
20     Rule.cascade)
21 class Book
22 {
23 private:
24     int _BookId;
25     string _Title;
26     Nullable!int _AuthorId;
27 public:
28     @PrimaryKeyColumn @NotNull
29     @property int BookId()
30     {
31         return _BookId;
32     }
33 
34     @Default!(2)
35     @property Nullable!int AuthorId()
36     {
37         return _AuthorId;
38     }
39     @property void AuthorId(N)(N value)
40         if (isNullable!(int , N))
41     {
42         setter(_AuthorId, value.to!(Nullable!int));
43     }
44     this(N)(int BookId_, string Title_, N AuthorId_)
45         if (isNullable!(int, N))
46     {
47         this._BookId = BookId_;
48         this._Title = Title_;
49         this._AuthorId = AuthorId_;
50         initializeKeyedItem();
51     }
52     Book dup()
53     {
54         return new Book(this._BookId, this._Title, this._AuthorId);
55     }
56 
57     mixin KeyedItem!();
58 
59 }
60 
61 version(unittest)
62 class Books
63 {
64     mixin KeyedCollection!(Book);
65 
66     static Books GetFromDB()
67     {
68         return new Books([
69                           new Book(1, "Emma", 1),
70                           new Book(2, "War and Peace", 2),
71                           new Book(3, "Catch XII", 3),
72                           new Book(4, "David Copperfield", 4),
73                           new Book(5, "Good as Gold", 3),
74                           new Book(6, "Anna Karenia", 2)
75                           ]);
76     }
77 }
78 unittest
79 {
80     auto authors = Authors.GetFromDB();
81     auto books = Books.GetFromDB();
82 
83     import std.exception : assertNotThrown;
84     assertNotThrown!ForeignKeyException(books.authors = authors);
85 
86     assert(books._authors.contains(1) && !books._authors.contains(5));
87     authors[1].AuthorId = 5;
88     assert(!books._authors.contains(1) && books._authors.contains(5));
89     assertNotThrown!ForeignKeyException(books.checkForeignKeys());
90 
91     assert(authors.length == 4);
92     assert(books.length == 6);
93     authors.remove(3);
94     assert(authors.length == 3);
95     assert(books.length == 4);
96 }
97 unittest
98 {
99     auto authors = Authors.GetFromDB();
100     auto books = Books.GetFromDB();
101     books.authors = authors;
102     assert(authors.length == 4);
103     assert(books._authors.length == 4);
104     books.authors = null;
105     assert(authors.length == 4);
106     assert(books._authors is null);
107 }
108 
109 unittest
110 {
111     auto authors = Authors.GetFromDB();
112     auto books = Books.GetFromDB();
113 
114     import std.algorithm : filter;
115     import std.exception : assertNotThrown, assertThrown;
116     assertNotThrown!ForeignKeyException(books.authors = authors);
117 
118     books.fk_Books_Authors_AuthorId_UpdateRule = Rule.setDefault;
119     static assert(hasDefault!(Book, "AuthorId"));
120     assert(hasDefault!(Book, "AuthorId"));
121     assert(authors.length == 4);
122     assert(books.length == 6);
123     int i = 0;
124     foreach(book; books.byValue.filter!(a => a.AuthorId == 2))
125         ++i;
126     assert(i == 2);
127 
128     i = 0;
129     foreach(book; books.byValue.filter!(a => a.AuthorId == 1))
130         ++i;
131     assert(i == 1);
132     authors[1].AuthorId = 5;
133     assert(authors.length == 4);
134     assert(books.length == 6);
135     i = 0;
136     foreach(book; books.byValue.filter!(a => a.AuthorId == 2))
137         ++i;
138     assert(i == 3);
139 
140     books.fk_Books_Authors_AuthorId_DeleteRule = Rule.setDefault;
141     authors.remove(3);
142     assert(authors.length == 3);
143     assert(books.length == 6);
144     i = 0;
145     foreach(book; books.byValue.filter!(a => a.AuthorId == 2))
146         ++i;
147     assert(i == 5);
148 }
149 
150 unittest
151 {
152     static assert(hasForeignKeys!(Book));
153     assert(hasForeignKeys!(Book));
154 }
155 
156 unittest
157 {
158     auto authors = Authors.GetFromDB();
159     auto books = Books.GetFromDB();
160 
161     books.authors = authors;
162     books.fk_Books_Authors_AuthorId_DeleteRule = Rule.setNull;
163     assert(authors.length == 4);
164     assert(books.length == 6);
165     authors.remove(3);
166     assert(authors.length == 3);
167     assert(books.length == 6);
168     import std.exception : assertNotThrown;
169     assertNotThrown!ForeignKeyException(books.checkForeignKeys());
170     authors[1].AuthorId = 5;
171     assert(authors.length == 3);
172     assert(books.length == 6);
173     int i = 0;
174     foreach(book; books)
175     {
176         if (book.AuthorId.isNull)
177         {
178             ++i;
179         }
180     }
181     assert(i == 2);
182     books.fk_Books_Authors_AuthorId_UpdateRule = Rule.setNull;
183     authors[5].AuthorId = 1;
184     i = 0;
185     foreach(book; books)
186     {
187         if (book.AuthorId.isNull)
188         {
189             ++i;
190         }
191     }
192     assert(i == 3);
193 }
194 
195 unittest
196 {
197     enum fkproperties =
198 `private Authors *_authors;
199 private Authors.key_type _changedAuthorsRow;
200 final @property void authors(ref Authors authors_)
201 {
202     this.authors = null;
203     this._authors = &authors_;
204     this._authors.collectionChanged.connect(&fk_Books_Authors_AuthorId_Changed);
205     checkForeignKeys();
206 }
207 final @property void authors(typeof(null) n)
208 {
209     if (this._authors !is null)
210     {
211         this._authors.collectionChanged.disconnect(&fk_Books_Authors_AuthorId_Changed);
212     this._authors = null;
213     }
214 }
215 `;
216     static assert(createForeignKeyProperties!(Book) == fkproperties);
217     assert(createForeignKeyProperties!(Book) == fkproperties);
218 }
219 
220 unittest
221 {
222     enum fkexceptions =
223 `if (this._authors !is null)
224 {
225     Authors.key_type i;
226     if(a.fk_Books_Authors_AuthorId_key(i))
227     {
228         enforceEx!ForeignKeyException(this._authors.contains(i), "fk_Books_Authors_AuthorId violation.");
229     }
230 }
231 `;
232     static assert(createForeignKeyCheckExceptions!(Book) == fkexceptions);
233     assert(createForeignKeyCheckExceptions!(Book) == fkexceptions);
234 }
235 
236 unittest
237 {
238     enum fkChanged =
239         `Rule fk_Books_Authors_AuthorId_UpdateRule = Rule.cascade;
240 Rule fk_Books_Authors_AuthorId_DeleteRule = Rule.cascade;
241 void fk_Books_Authors_AuthorId_Changed(string propertyName, Authors.key_type item_key)
242 {
243     if (canFind(["AuthorId"], propertyName))
244     {
245         this._changedAuthorsRow = item_key;
246     }
247     else if (propertyName == "key")
248     {
249         auto changedAuthors = this.byValue.filter!(
250             (Book a) =>
251             {
252                 Authors.key_type i;
253                 return (a.fk_Books_Authors_AuthorId_key(i) ? i == this._changedAuthorsRow : false);
254             }());
255         final switch (fk_Books_Authors_AuthorId_UpdateRule) with (Rule)
256         {
257         case noAction:
258             break;
259         case restrict:
260             if (!changedAuthors.empty)
261                 throw new ForeignKeyException("fk_Books_Authors_AuthorId violation.");
262             break;
263         case setNull:
264         static if (__traits(compiles,
265                             (Book a)
266                             {
267                                 a.AuthorId = null;
268                             }))
269             {
270                 changedAuthors.each!(
271                     (Book a) =>
272                     {
273                         a.AuthorId = null;
274                     }());
275                 break;
276             }
277             else
278             {
279                 throw new ForeignKeyException("fk_Books_Authors_AuthorId. Cannot use Rule.setNull when the member cannot be set to null.");
280             }
281         case setDefault:
282             changedAuthors.each!(
283                 (Book a) =>
284                 {
285                     static if (hasDefault!(Book, "AuthorId"))                    {
286                         a.AuthorId = GetDefault!(Book, "AuthorId");
287                     }
288                     else
289                     {
290                         a.AuthorId = typeof(a.AuthorId).init;
291                     }
292                 }());
293             break;
294         case cascade:
295             changedAuthors.each!(
296                 (Book a) =>
297                 {
298                     a.AuthorId = item_key.AuthorId;
299                 }());
300             break;
301         }
302     }
303     else if (propertyName == "remove")
304     {
305         auto removedAuthors = this.byValue.filter!(
306             (Book a) =>
307             {
308                 Authors.key_type i;
309                 return (a.fk_Books_Authors_AuthorId_key(i) ? i == item_key : false);
310             }());
311         final switch (fk_Books_Authors_AuthorId_DeleteRule) with (Rule)
312         {
313         case noAction:
314             break;
315         case restrict:
316             if (!removedAuthors.empty)
317                 throw new ForeignKeyException("fk_Books_Authors_AuthorId violation.");
318             break;
319         case setNull:
320         static if (__traits(compiles,
321                             (Book a)
322                             {
323                                 a.AuthorId = null;
324                             }))
325             {
326                 removedAuthors.each!(
327                     (Book a) =>
328                     {
329                         a.AuthorId = null;
330                     }());
331                 break;
332             }
333             else
334             {
335                 throw new ForeignKeyException("fk_Books_Authors_AuthorId. Cannot use Rule.setNull when the member cannot be set to null.");
336             }
337         case setDefault:
338             removedAuthors.each!(
339                 (Book a) =>
340                 {
341                     static if (hasDefault!(Book, "AuthorId"))                    {
342                         a.AuthorId = GetDefault!(Book, "AuthorId");
343                     }
344                     else
345                     {
346                         a.AuthorId = typeof(a.AuthorId).init;
347                     }
348                 }());
349             break;
350         case cascade:
351             removedAuthors.each!(
352                 (Book a) =>
353                 {
354                     this.remove(a.key);
355                 }());
356             break;
357         }
358     }
359 }
360 `;
361     static assert(createForeignKeyChanged!(Book) == fkChanged);
362     assert(createForeignKeyChanged!(Book) == fkChanged);
363 }
364 
365 unittest
366 {
367     enum fkproperties =
368 `final bool fk_Books_Authors_AuthorId_key(out Authors.key_type aKey)
369 {
370     bool result;
371     static if (
372         is(typeof(aKey.AuthorId) == typeof(this.AuthorId))
373         )
374     {
375         aKey.AuthorId = this.AuthorId;
376         result = true;
377     }
378     else static if (__traits(compiles,
379                              (Book b)
380                              {
381                                  if (b.AuthorId.isNull == true) { }
382                              }))
383     {
384         if (
385             !this.AuthorId.isNull
386            )
387         {
388             aKey.AuthorId = this.AuthorId;
389             result = true;
390         }
391         else
392         {
393             result = false;
394         }
395     }
396     else
397     {
398         static assert(false, "Column type mismatch for fk_Books_Authors_AuthorId.");
399     }
400     return result;
401 }
402 `;
403 
404     static assert(createForeignKeyPropertyConverter!(Book) == fkproperties, createForeignKeyPropertyConverter!(Book));
405     assert(createForeignKeyPropertyConverter!(Book) == fkproperties, createForeignKeyPropertyConverter!(Book));
406 }
407 
408 unittest
409 {
410     auto authors = Authors.GetFromDB();
411     auto books = Books.GetFromDB();
412 
413     books.authors = authors;
414     books.fk_Books_Authors_AuthorId_UpdateRule = Rule.restrict;
415     books.fk_Books_Authors_AuthorId_DeleteRule = Rule.restrict;
416 
417     import std.exception : assertThrown;
418 
419     assertThrown!ForeignKeyException(authors.remove(3));
420 
421     assert(!authors.contains(18));
422     assertThrown!ForeignKeyException(books[1].AuthorId = 18);
423 }